import cloneDeep from 'lodash.clonedeep';
import produce, { setAutoFreeze } from 'immer';

import {
  CHANGE_VIEW_MODE,
  CLEAR_VIEWPORT,
  SET_ACTIVE_SPECIFIC_DATA,
  SET_SPECIFIC_DATA,
  SET_VIEWPORT,
  SET_VIEWPORT_ACTIVE,
  SET_VIEWPORT_LAYOUT,
  SET_IMAGE_ACTIVE,
  SET_IMAGE_LAYOUT,
  SET_VIEWPORT_LAYOUT_AND_DATA,
  SET_ACTIVE_VIEWPORT_VR_DATA,
  SET_VIEWPORT_MAXIMIZED,
  SET_MPR_LAYOUT,
} from './../constants/ActionTypes.js';

setAutoFreeze(false);

export const DEFAULT_STATE = {
  viewMode: 'normal',  // [0] normal, [1] maximized, [2] mpr, [3] vr.
  normal: {
    numRows: 1,
    numColumns: 1,
    activeViewportIndex: 0,
    layout: {
      viewports: [{
        numRows: 1,
        numColumns: 1,
        activeViewportIndex: 0,
      }],
    },
    viewportSpecificData: {},
  },
  max: {
    numRows: 1,
    numColumns: 1,
    activeViewportIndex: 0,
    layout: {
      viewports: []
    },
    viewportSpecificData: {},
  },
  mpr: {
    numRows: 2,
    numColumns: 2,
    merge: 'L',
    activeViewportIndex: 0,
    layout: {
      viewports: [{
        details: {
          orientation: {
            sliceNormal: [0, 0, 1],
            viewUp: [0, -1, 0],
          },
          style: { gridRow: '1 / 3' },
        },
      }, {
        details: {
          orientation: {
            sliceNormal: [1, 0, 0],
            viewUp: [0, 0, 1],
          },
        },
      }, {
        details: {
          orientation: {
            sliceNormal: [0, 1, 0],
            viewUp: [0, 0, 1],
          },
        },
      }]
    },
    viewportSpecificData: {},
  },
};

/**
 *  Take the new number of Rows and Columns, delete all not used viewport data and also set
 *  active viewport as default in case current one is not available anymore.
 *
 * @param {Number} numRows
 * @param {Number} numColumns
 * @param {Object} currentViewportSpecificData
 * @returns
 */
const findActiveViewportSpecificData = (
  numRows,
  numColumns,
  currentViewportSpecificData = {}
) => {
  const numberOfViewports = numRows * numColumns;
  const viewportSpecificData = cloneDeep(currentViewportSpecificData);

  if (numberOfViewports < Object.keys(viewportSpecificData).length) {
    Object.keys(viewportSpecificData).forEach(key => {
      if (key > numberOfViewports - 1) {
        delete viewportSpecificData[key];
      }
    });
  }

  return viewportSpecificData;
};
/**
 *  Take new number of Rows and Columns and make sure the current active viewport index is still available, if not, return the default
 *
 * @param {Number} numRows
 * @param {Number} numColumns
 * @param {Number} currentActiveViewportIndex
 * @returns
 */
const getActiveViewportIndex = (
  numRows,
  numColumns,
  currentActiveViewportIndex
) => {
  const numberOfViewports = numRows * numColumns;

  return currentActiveViewportIndex > numberOfViewports - 1
    ? DEFAULT_STATE.activeViewportIndex
    : currentActiveViewportIndex;
};

/**
 * The definition of a viewport action.
 *
 * @typedef {Object} ViewportAction
 * @property {string} type -
 * @property {Object} data -
 * @property {Object} layout -
 * @property {number} viewportIndex -
 * @property {Object} viewportSpecificData -
 */

/**
 * @param {Object} [state=DEFAULT_STATE] The current viewport state.
 * @param {ViewportAction} action A viewport action.
 */
const viewports = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case CHANGE_VIEW_MODE: {
      return produce(state, draftState => {
        if (draftState.viewMode === action.viewMode || (!action.viewMode)) return;
        // if (draftState.viewMode !== 'normal') {
        //   // clear
        //   draftState[draftState.viewMode] = DEFAULT_STATE[draftState.viewMode];
        // }
        if (draftState.viewMode === 'max') {
          // clear
          draftState[draftState.viewMode] = DEFAULT_STATE[draftState.viewMode];
        }
        draftState.viewMode = action.viewMode;
      });
    }

    /**
     * Sets the active viewport index.
     *
     * @return {Object} New state.
     */
    case SET_VIEWPORT_ACTIVE: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        modeState.activeViewportIndex = getActiveViewportIndex(
          modeState.numRows,
          modeState.numColumns,
          action.viewportIndex
        );
        modeState.layout.viewports[modeState.activeViewportIndex].activeViewportIndex = action.imageViewportIndex;
      });
    }

    case SET_VIEWPORT_MAXIMIZED: {
      return produce(state, draftState => {
        if (draftState.viewMode === 'max') {
          draftState.max.viewportSpecificData[0] = {};
          draftState.max.layout.viewports[0] = {};
          draftState.viewMode = 'normal';
        } else {
          const modeState = draftState[draftState.viewMode];
          draftState.max.viewportSpecificData[0] = { ...modeState.viewportSpecificData[action.viewportIndex] };
          draftState.max.layout.viewports[0] = { ...modeState.layout.viewports[action.viewportIndex], numRows: 1, numColumns: 1 };
          draftState.viewMode = 'max';
        }
      });
    }

    case SET_MPR_LAYOUT: {
      return produce(state, draftState => {
        const { numRows, numColumns, merge, viewports } = action;
        const mprState = draftState.mpr;
        mprState.numRows = numRows;
        mprState.numColumns = numColumns;
        mprState.merge = merge;
        mprState.layout.viewports = viewports;
      });
    }

    /**
     * Sets viewport layout.
     *
     * @return {Object} New state.
     */
    case SET_VIEWPORT_LAYOUT: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        const { numRows, numColumns, viewports } = action;
        modeState.numRows = numRows;
        modeState.numColumns = numColumns;
        modeState.layout = { viewports: [...viewports] };
        modeState.viewportSpecificData = findActiveViewportSpecificData(
          numRows,
          numColumns,
          modeState.viewportSpecificData
        );
        modeState.activeViewportIndex = getActiveViewportIndex(
          numRows,
          numColumns,
          modeState.activeViewportIndex
        );
      });
    }

    case SET_IMAGE_LAYOUT: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        const { numRows, numColumns, activeViewportIndex } = action;
        const v = modeState.layout.viewports[modeState.activeViewportIndex];
        modeState.layout.viewports[modeState.activeViewportIndex] = {
          ...v,
          numRows,
          numColumns,
          activeViewportIndex,
        };
      });
    }

    case SET_IMAGE_ACTIVE: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        modeState.layout.viewports[modeState.activeViewportIndex].activeImageIndex = action.activeImageIndex;
      });
    }

    /**
     * Sets viewport layout and data.
     *
     * @return {Object} New state.
     */
    case SET_VIEWPORT_LAYOUT_AND_DATA: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        const { numRows, numColumns, viewportSpecificData, viewports } = action;
        modeState.numRows = numRows;
        modeState.numColumns = numColumns;
        modeState.layout = { viewports: [...viewports] };
        modeState.viewportSpecificData = findActiveViewportSpecificData(
          numRows,
          numColumns,
          viewportSpecificData
        );
        modeState.activeViewportIndex = getActiveViewportIndex(
          numRows,
          numColumns,
          modeState.activeViewportIndex
        );
      });
    }

    /**
     * Sets viewport specific data of active viewport.
     *
     * @return {Object} New state.
     */
    case SET_VIEWPORT: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        modeState.viewportSpecificData[action.viewportIndex] =
          modeState.viewportSpecificData[action.viewportIndex] || {};

        Object.keys(action.viewportSpecificData).forEach(key => {
          modeState.viewportSpecificData[action.viewportIndex][key] =
            action.viewportSpecificData[key];
        });

        if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
          modeState.layout.viewports[action.viewportIndex].plugin =
            action.viewportSpecificData.plugin;
        }
      });
    }

    /**
     * Sets viewport specific data of active/any viewport.
     *
     * @return {Object} New state.
     */
    case SET_ACTIVE_VIEWPORT_VR_DATA: {
      return produce(state, draftState => {
        const { viewport, viewportSpecificData } = action;
        const modeState = draftState[draftState.viewMode];
        const viewportIndex = action.viewportIndex
          ? action.viewportIndex
          : modeState.activeViewportIndex;
        modeState.viewportSpecificData[viewportIndex] = {
          ...viewportSpecificData
        };
        modeState.layout.viewports[viewportIndex] = {
          ...viewport
        };
      });
    }
    case SET_ACTIVE_SPECIFIC_DATA: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        const viewportIndex = action.viewportIndex
          ? action.viewportIndex
          : modeState.activeViewportIndex;

        modeState.viewportSpecificData[viewportIndex] = {
          ...action.viewportSpecificData,
        };

        if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
          modeState.layout.viewports[viewportIndex].plugin =
            action.viewportSpecificData.plugin;
        }
      });
    }

    /**
     * Clears viewport specific data of any viewport.
     *
     * @return {Object} New state.
     */
    case CLEAR_VIEWPORT: {
      return produce(state, draftState => {
        const modeState = draftState[draftState.viewMode];
        if (action.viewportIndex) {
          modeState.viewportSpecificData[action.viewportIndex] = {};
        } else {
          draftState[draftState.viewMode] = DEFAULT_STATE[draftState.viewMode];
        }
      });
    }

    /**
     * Returns the current application state.
     *
     * @return {Object} The current state.
     */
    default: {
      return state;
    }
  }
};

export default viewports;
